#include <windows.h>
#include <stdint.h>
#include <stdio.h>

#include "hs_window.h"
#include "hs_log.h"
// *******************************************************
// 
// 
// TODO сделать более элегантную сборку структур в WndProc
// 
// 
// ******************************************************* 

LRESULT CALLBACK Proc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) {
  Wnd* Window = (Wnd*)GetWindowLongPtr(hwnd, -21);
  if (Window) {
    switch (msg) {
      case WM_CLOSE:
        break;
      case WM_DESTROY:
        Window->SetRunning(false);
        break;
      case WM_SETFOCUS:
        if (Window->GetFullScreen()) {
          Window->SetMode(Window->GetClientWidth(), Window->GetClientHeight(), Window->GetFullScreen());
        }
        break;
      case WM_KILLFOCUS:
        if (Window->GetFullScreen()) {
          Window->RestoreMode();
          ShowWindow(hwnd, SW_MINIMIZE);
        }
        break;
      case WM_SIZE:
        break;
      // keydown+transliemessage
      case WM_CHAR: //UTF-16
      case WM_UNICHAR:  //(UTF)-32
      //keyup+translitemessage
      case WM_DEADCHAR:

      //press alt+key
      //wm_sysdeadchar -> syskeydown+translatemessage

      case WM_SYSKEYUP:
      case WM_SYSKEYDOWN:
      case WM_SYSDEADCHAR:
        
      case WM_KEYUP:
      case WM_KEYDOWN:
        t_event_key key;
        key.Id = wParam;
        key.lParam = lParam;
        key.Alt = (msg == WM_SYSKEYDOWN) | (msg == WM_SYSKEYUP) | (msg == WM_SYSDEADCHAR);
        key.State = ((msg == WM_CHAR) | (msg == WM_UNICHAR) | (msg == WM_SYSKEYDOWN) | 
          (msg == WM_SYSDEADCHAR) | (msg == WM_KEYDOWN) ? KS_DOWN : KS_UP);
        key.Property = ((msg == WM_KEYDOWN) | (msg == WM_KEYUP) | 
          (msg == WM_SYSKEYDOWN) | (msg == WM_SYSKEYUP) ? KP_VIRTUALKEY : KP_CHARCODE);
        Window->ProcKey(&key);
        break;
      case WM_LBUTTONDBLCLK:
      case WM_LBUTTONDOWN:
      case WM_LBUTTONUP:

      case WM_MBUTTONDBLCLK:
      case WM_MBUTTONDOWN:
      case WM_MBUTTONUP:

      case WM_MOUSEMOVE:

      case WM_MOUSEHWHEEL:
      case WM_MOUSEWHEEL:

      case WM_RBUTTONDBLCLK:
      case WM_RBUTTONDOWN:
      case WM_RBUTTONUP:

      case WM_XBUTTONDBLCLK:
      case WM_XBUTTONDOWN:
      case WM_XBUTTONUP:
        t_event_mouse mouse;
        mouse.State = MOUSE_STATE_CONST[msg - WM_MOUSEFIRST];
        for (int i = MC_CTRL; i <= MC_X2BTN; ++i) {
          mouse.Controls[i] = (wParam and MOUSE_CONTROL_CONST[i]) > 0;         
        } 
        mouse.X = LOWORD(lParam);
        mouse.Y = HIWORD(lParam);
        mouse.Wheel = (msg == WM_MOUSEWHEEL ? (signed short int)(wParam >> 16) / WHEEL_DELTA : 0);
        mouse.HWheel = (msg == WM_MOUSEHWHEEL ? (signed short int)(wParam >> 16) / WHEEL_DELTA : 0);
        Window->ProcMouse(&mouse);
        break;

    }
  }
  return DefWindowProc(hwnd, msg, wParam, lParam);
}

Wnd::Wnd(int _width, int _height, bool _fullscr, const char* _caption, t_proc_mouse _mouse, t_proc_key _key)
  : ExStyle({WS_EX_WINDOWEDGE | WS_EX_CLIENTEDGE, 0})
  , Style({WS_CLIPSIBLINGS | WS_CLIPCHILDREN | WS_OVERLAPPED | WS_CAPTION | 
      WS_MINIMIZEBOX | WS_SYSMENU, WS_SYSMENU | WS_CLIPSIBLINGS | WS_CLIPCHILDREN})
  , Cls({sizeof(WNDCLASSEX), CS_VREDRAW | CS_HREDRAW | CS_OWNDC, &Proc, 0, 0, 0,
      LoadIcon(Cls.hInstance, IDI_APPLICATION), LoadCursor(Cls.hInstance, IDC_ARROW), 
      (HBRUSH)COLOR_WINDOW, NULL, _caption, 0})
  , ProcMouse(_mouse)
  , ProcKey(_key)
  , Caption(_caption)
  , FullScreen(_fullscr) 
{
  if (!RegisterClassEx(&Cls)) {
    log("!Error wnd_create RegisterClassEx\n");
  }
  Handle = CreateWindowEx(0, Cls.lpszClassName, Caption, 0, 
    0, 0, 0, 0, 0, 0, Cls.hInstance, NULL);
  if (!Handle) {
    log("!Error wnd_create CreateWindowEx\n");
  }
  if (!SetWindowLongPtr(Handle, -21, (long int)this)) {
    log("!Error wnd_create SetWindowLong");
  }
  hDC = GetDC(Handle);
  if (!hDC) {
    log("!Error wnd_create GetDC");
  }
  if (!SetMode(_width, _height, _fullscr)) {
    log("!Error wnd_create wnd_setmode\n");
  };
  SetRunning(true);
};

Wnd::~Wnd() {
  ReleaseDC(Handle, hDC);
  if (!UnregisterClass(Cls.lpszClassName, Cls.hInstance)) {
    log("!Error wnd_destroy UnregisterClass");
  }
  if (FullScreen) {
    RestoreMode();
  }
};

bool Wnd::SetMode(int _width, int _height, bool _fullscr) {
  X = 0; 
  Y = 0;
  Width = 0;
  Height = 0;
  ClientWidth = _width;
  ClientHeight = _height;

  SetWindowLongPtr(Handle, GWL_EXSTYLE, 0);
  SetWindowLongPtr(Handle, GWL_STYLE, 0);
  SetWindowPos(Handle, 0, 0, 0, 0, 0, 0);
  if (_fullscr) {
    Width = ClientWidth;
    Height = ClientHeight;
    DEVMODE mode;
    mode.dmSize = sizeof(DEVMODE);
    mode.dmFields = DM_PELSWIDTH | DM_PELSHEIGHT;
    mode.dmPelsWidth = Width;
    mode.dmPelsHeight = Height;
    if (ChangeDisplaySettings(&mode, CDS_FULLSCREEN | CDS_TEST) ==  DISP_CHANGE_SUCCESSFUL) {
      ChangeDisplaySettings(&mode, CDS_FULLSCREEN);
    } else {
      log("!Error wnd_setmode ChangeDisplaySettings\n");
      return false;            
    };
  } else {
    if (FullScreen != _fullscr) {
      RestoreMode();
    }
    Width = ClientWidth + GetSystemMetrics(SM_CXBORDER) * 2 + GetSystemMetrics(SM_CXFRAME) * 2;
    Height = ClientHeight + GetSystemMetrics(SM_CYBORDER) * 2 + GetSystemMetrics(SM_CYCAPTION) + GetSystemMetrics(SM_CYFRAME) * 2;
    X = (GetSystemMetrics(SM_CXSCREEN) - Width) / 2;
    Y = (GetSystemMetrics(SM_CYSCREEN) - Height) / 2;                  
  };
  FullScreen = _fullscr;
  SetWindowLongPtr(Handle, GWL_EXSTYLE, ExStyle[FullScreen]);
  SetWindowLongPtr(Handle, GWL_STYLE, Style[FullScreen]);
  SetWindowPos(Handle, 0, X, Y, Width, Height, SWP_SHOWWINDOW | SWP_DRAWFRAME);
  return true;
};

bool Wnd::SetCaption(const char* _caption) {
  Caption = _caption;
  return SetWindowText(Handle, Caption);
};

void Wnd::Run() {
  MSG msg;
  while (Running) {
    while (PeekMessage(&msg, 0, 0, 0, 1)) {
      TranslateMessage(&msg);
      DispatchMessage(&msg);
    }

  }  
};